using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

using phorm.config;
using phorm.database.core;
using phorm.model.fields;

namespace phorm.utils
{
    /// <summary>
    /// XML Utilities for generating XML of Database objects.
    /// </summary>
    public class XMLUtils
    {
	    /// <summary>
	    /// The number of levels to dive into. 0 = as many as possible
	    /// </summary>
        private static int _levels;

	    /// <summary>
	    /// Used to record the information of a single DatabaseColumn
	    /// </summary>
	    /// <param name="key">The name of the column</param>
	    /// <param name="column">The column object</param>
        public static void recordInformation(string key, DatabaseColumn column)
	    {
            /* HTTP headers to avoid caching this data
            header('Content-Type: application/xml');
		    header('Cache-Control: no-store, must-revalidate');
            */

            XmlDocument dom = new XmlDocument();
            XmlElement rootNode = dom.CreateElement("Record");
            XmlElement childNode = dom.CreateElement("key");
            childNode.AppendChild(dom.CreateCDataSection(key));
            rootNode.AppendChild(childNode);
            childNode = dom.CreateElement("value");
            childNode.AppendChild(dom.CreateCDataSection(column.value.ToString()));
            rootNode.AppendChild(childNode);
            dom.AppendChild(rootNode);
            dom.Save(Console.Out);
	    }

        /// <summary>
        /// Take a list of DatabaseRecords and generate XML. Goes in depth according to levels.
        /// </summary>
        /// <param name="collection">The collection of record objects</param>
        /// <param name="levels">Number of levels to go into. 0 = infinite</param>
        public static void collectionToXML(List<DatabaseRecord> collection, int levels)
        {
            /* HTTP headers to avoid caching this data
            header('Content-Type: application/xml');
            header('Cache-Control: no-store, must-revalidate');
    		*/

            XMLUtils._levels = levels;

            XmlDocument dom = new XmlDocument();
            XmlElement rootNode = dom.CreateElement("ArrayCollection");

            foreach(DatabaseRecord record in collection)
            {
                XmlElement firstChildNode = dom.CreateElement(record.GetType().Name.ToString());

                foreach(KeyValuePair<string, DatabaseColumn> column in record.columns)
                {
                    if (XMLUtils._levels > 1 || XMLUtils._levels == 0)
                    {
                        int currentLevel = 1;

                        Type foreignKeyType = typeof(ForeignKey<>);
                        DatabaseColumn tmp = column.Value as DatabaseColumn;
                        Type currentType = tmp.GetType();

                        if (currentType.Name.Equals(foreignKeyType.Name))
                        {
                            DatabaseRecord relative = tmp.GetType().GetField("relative").GetValue(tmp) as DatabaseRecord;
                            PrimaryKey primaryKey = relative.GetType().GetField("primary_key").GetValue(relative) as PrimaryKey;
                            XMLUtils.processForeignKey(relative, primaryKey, dom, firstChildNode, currentLevel);
                        }
                        else if (column.Value is BaseManyToMany)
                        {
                            XMLUtils.processManyToMany(column.Value as BaseManyToMany, dom, firstChildNode, currentLevel);
                        }
                    }

                    if(!(column.Value is IIgnorableColumn) && !(column.Value is IJoinableColumn))
                    {
                        XmlElement childNode = dom.CreateElement(column.Key);
                        childNode.AppendChild(dom.CreateCDataSection(column.Value.value.ToString().Replace(@"\", "")));
                        firstChildNode.AppendChild(childNode);
                    }

                    if (column.Value is UnixTimestamp)
                        XMLUtils.formatUnixTimestamp(column.Value as UnixTimestamp, dom, firstChildNode);
                }
                rootNode.AppendChild(firstChildNode);
            }
            dom.AppendChild(rootNode);
            dom.Save(Console.Out);
        }

        /// <summary>
        /// Process a Many to Many column to get data and put into XML.
        /// </summary>
        /// <param name="manyToMany">The many-to-many column object</param>
        /// <param name="dom">The XmlDocument object to modify</param>
        /// <param name="firstChildNode">The first child in the XML</param>
        /// <param name="currentLevel">The current number of levels that have been gone into</param>
        private static void processManyToMany(BaseManyToMany manyToMany, XmlDocument dom, XmlElement firstChildNode, int currentLevel)
        {
            if (currentLevel >= 0) currentLevel++;
    		    List<DatabaseRecord> records = manyToMany.all();

            if (records.Count > 0)
            {
                XmlElement childNode = dom.CreateElement(records[0].GetType().ToString());
                foreach (DatabaseRecord record in records)
                {
                    XmlElement container = dom.CreateElement(record.GetType().ToString().ToLower());

                    foreach(KeyValuePair<string, DatabaseColumn> column in record.columns)
                    {
                        if (!(column.Value is IIgnorableColumn) && !(column.Value is IJoinableColumn))
                        {
                            XmlElement related = dom.CreateElement(column.Key);
                            related.AppendChild(dom.CreateCDataSection(column.Value.value.ToString().Replace(@"\", "")));
                            container.AppendChild(related);
                        }
                        else
                        {
                            Type foreignKeyType = typeof(ForeignKey<>);
                            DatabaseColumn tmp = column.Value as DatabaseColumn;
                            Type currentType = tmp.GetType();

                            if (currentType.Name.Equals(foreignKeyType.Name) && (currentLevel < XMLUtils._levels || XMLUtils._levels == 0))
                            {
                                DatabaseRecord relative = tmp.GetType().GetField("relative").GetValue(tmp) as DatabaseRecord;
                                PrimaryKey primaryKey = relative.GetType().GetField("primary_key").GetValue(relative) as PrimaryKey;
                                XMLUtils.processForeignKey(relative, primaryKey, dom, firstChildNode, currentLevel);
                            }
                            else if (column.Value is BaseManyToMany && (currentLevel < XMLUtils._levels || XMLUtils._levels == 0))
                            {
                                XMLUtils.processManyToMany(column.Value as BaseManyToMany, dom, childNode, currentLevel);
                            }
                        }
                    }
                    childNode.AppendChild(container);
                }
                firstChildNode.AppendChild(childNode);
            }
        }

        /// <summary>
        /// Process a ForeignKey and generate XML based on the data.
        /// </summary>
        /// <param name="foreignKey">The foreign key object</param>
        /// <param name="dom">The XmlDocument object to modify</param>
        /// <param name="firstChildNode">The first child in the XML</param>
        /// <param name="currentLevel">The current number of levels that have been gone into</param>
        private static void processForeignKey(DatabaseRecord relative, PrimaryKey primaryKey, XmlDocument dom, XmlElement firstChildNode, int currentLevel)
        {
            if (currentLevel >= 0) currentLevel++;

            XmlElement childNode = dom.CreateElement(relative.GetType().Name.ToString());

            XmlAttribute attribute = dom.CreateAttribute("value");
            attribute.AppendChild(dom.CreateTextNode(primaryKey.value.ToString()));
            childNode.SetAttributeNode(attribute);

            DatabaseRecord record = null;
            if (currentLevel > 2)
            {
                object id = primaryKey.value;
                record = Activator.CreateInstance(relative.GetType()) as DatabaseRecord;
                record.initWithId(id);
            }
            else
            {
                record = relative;
            }

            foreach (KeyValuePair<string, DatabaseColumn> column in record.columns)
            {
                if (!(column.Value is IIgnorableColumn) && !(column.Value is IJoinableColumn))
                {
                    XmlElement related = dom.CreateElement(column.Key);
                    related.AppendChild(dom.CreateCDataSection(column.Value.value.ToString().Replace(@"\", "")));
                    childNode.AppendChild(related);
                }
                else
                {
                    Type foreignKeyType = typeof(ForeignKey<>);
                    DatabaseColumn tmp = column.Value as DatabaseColumn;
                    Type currentType = tmp.GetType();

                    if (currentType.Name.Equals(foreignKeyType.Name) && (currentLevel < XMLUtils._levels || XMLUtils._levels == 0))
                    {
                        DatabaseRecord altRelative = tmp.GetType().GetField("relative").GetValue(tmp) as DatabaseRecord;
                        PrimaryKey altPrimaryKey = altRelative.GetType().GetField("primary_key").GetValue(altRelative) as PrimaryKey;
                        XMLUtils.processForeignKey(altRelative, altPrimaryKey, dom, firstChildNode, currentLevel);
                    }
                    else if (column.Value is BaseManyToMany && (currentLevel < XMLUtils._levels || XMLUtils._levels == 0))
                    {
                        XMLUtils.processManyToMany(column.Value as BaseManyToMany, dom, childNode, currentLevel);
                    }
                }

                if (column.Value is UnixTimestamp)
                    XMLUtils.formatUnixTimestamp(column.Value as UnixTimestamp, dom, childNode);
            }
            firstChildNode.AppendChild(childNode);
        }

        /// <summary>
        /// Format a UNIX Timestamp as a date string configured by PhormParams.
        /// </summary>
        /// <param name="column">The timestamp column object</param>
        /// <param name="dom">The XmlDocument object to modify</param>
        /// <param name="firstChildNode">The first child in the XML</param>
        private static void formatUnixTimestamp(UnixTimestamp column, XmlDocument dom, XmlElement firstChildNode)
        {
            DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0);
            dateTime = dateTime.AddSeconds(double.Parse(column.value.ToString()));

            XmlElement childNode = dom.CreateElement(column.column + "_format");
            childNode.AppendChild(dom.CreateCDataSection(dateTime.ToString(PhormConfig.phormParams.dateFormat)));
            firstChildNode.AppendChild(childNode);
        }
    }
}
